{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将所有特征串联起来，构成RS_Train.csv\n",
    "#RS_Test.csv\n",
    "#为最后推荐系统做准备\n",
    "from __future__ import division\n",
    "\n",
    "import pickle\n",
    "import numpy as np\n",
    "import scipy.io as sio\n",
    "import scipy.sparse as ss\n",
    "from numpy.random import random  \n",
    "from collections import defaultdict\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "class RecommonderSystem:\n",
    "    def __init__(self):\n",
    "        \n",
    "      \n",
    "        # 读入数据做初始化\n",
    "\n",
    "        #用户和活动新的索引\n",
    "        self.userIndex = pickle.load(open(\"PE_userIndex.pkl\", 'rb'))\n",
    "        self.eventIndex = pickle.load(open(\"PE_eventIndex.pkl\", 'rb'))\n",
    "        self.n_users = len(self.userIndex)\n",
    "        self.n_items = len(self.eventIndex)\n",
    "\n",
    "        #用户-活动关系矩阵R\n",
    "        #在train_SVD会重新从文件中读取,二者要求的格式不同，来不及统一了:(\n",
    "        self.userEventScores = sio.mmread(\"PE_userEventScores\").todense()\n",
    "\n",
    "        #倒排表\n",
    "        ##每个用户参加的事件\n",
    "        self.itemsForUser = pickle.load(open(\"PE_eventsForUser.pkl\", 'rb'))\n",
    "        ##事件参加的用户\n",
    "        self.usersForItem = pickle.load(open(\"PE_usersForEvent.pkl\", 'rb'))\n",
    "\n",
    "        #基于模型的协同过滤参数初始化,训练\n",
    "        self.init_SVD()\n",
    "        self.train_SVD(trainfile = \"train.csv\")\n",
    "\n",
    "        #根据用户属性计算出的用户之间的相似度\n",
    "        self.userSimMatrix = sio.mmread(\"US_userSimMatrix\").todense()\n",
    "\n",
    "        #根据活动属性计算出的活动之间的相似度\n",
    "        self.eventPropSim = sio.mmread(\"EV_eventPropSim\").todense()\n",
    "        self.eventContSim = sio.mmread(\"EV_eventContSim\").todense()\n",
    "\n",
    "        #每个用户的朋友的数目\n",
    "        self.numFriends = sio.mmread(\"UF_numFriends\")\n",
    "        #用户的每个朋友参加活动的分数对该用户的影响\n",
    "        self.userFriends = sio.mmread(\"UF_userFriends\").todense()\n",
    "\n",
    "        #活动本身的热度\n",
    "        self.eventPopularity = sio.mmread(\"EA_eventPopularity\").todense()\n",
    "\n",
    "    def init_SVD(self, K=20):\n",
    "        #初始化模型参数（for 基于模型的协同过滤SVD_CF）\n",
    "        self.K = K  \n",
    "\n",
    "        #init parameters\n",
    "        #bias\n",
    "        self.bi = np.zeros(self.n_items)  \n",
    "        self.bu = np.zeros(self.n_users)  \n",
    "\n",
    "        #the small matrix\n",
    "        self.P = random((self.n_users,self.K))/10*(np.sqrt(self.K))\n",
    "        self.Q = random((self.K, self.n_items))/10*(np.sqrt(self.K))  \n",
    "\n",
    "\n",
    "    def train_SVD(self,trainfile = 'train.csv', steps=100,gamma=0.5,Lambda=0.15):\n",
    "        #训练SVD模型（for 基于模型的协同过滤SVD_CF）\n",
    "        #gamma：为学习率\n",
    "        #Lambda：正则参数\n",
    "\n",
    "        #偷懒了，为了和原来的代码的输入接口一样，直接从训练文件中去读取数据\n",
    "        print(\"SVD Train...\") \n",
    "        ftrain = open(trainfile, 'r')\n",
    "        ftrain.readline()\n",
    "        self.mu = 0.0\n",
    "        n_records = 0\n",
    "        uids = []  #每条记录的用户索引\n",
    "        i_ids = [] #每条记录的item索引\n",
    "        #用户-Item关系矩阵R（内容同userEventScores相同），临时变量，训练完了R不再需要\n",
    "        R = np.zeros((self.n_users, self.n_items))\n",
    "\n",
    "        for line in ftrain:\n",
    "            cols = line.strip().split(\",\")\n",
    "            u = self.userIndex[cols[0]]  #用户\n",
    "            i = self.eventIndex[cols[1]] #活动\n",
    "\n",
    "            uids.append(u)\n",
    "            i_ids.append(i)\n",
    "\n",
    "            R[u,i] = int(cols[4])  #interested\n",
    "            self.mu += R[u,i]\n",
    "            n_records += 1\n",
    "\n",
    "        ftrain.close()\n",
    "        self.mu /= n_records\n",
    "\n",
    "        # 请补充完整SVD模型训练过程\n",
    "        # 采用随机梯度下降\n",
    "        # 用于存储更新前的P，用于计算Q\n",
    "        result = np.zeros(steps)\n",
    "                \n",
    "        for step in range(steps):\n",
    "            temp_P = np.zeros(self.K)   \n",
    "            rmse = 0\n",
    "#             train_size = 100\n",
    "            #梯度下降\n",
    "            for index, u in enumerate(uids):\n",
    "                i = i_ids[index]\n",
    "                #计算残差\n",
    "                eui = R[u,i] - self.pred_SVD(u,i)\n",
    "                rmse += eui ** 2 \n",
    "                     \n",
    "            #计算更新后的Puk\n",
    "            for k in range(0,self.K):\n",
    "                    temp_P[k] = self.P[u,k]\n",
    "                    self.P[u,k] = self.P[u,k] + gamma * (eui * self.Q[k,i] - Lambda * self.P[u,k]) \n",
    "                 \n",
    "            #计算更新后的Qki\n",
    "            for k in range(0,self.K):\n",
    "                self.Q[k,i] = self.Q[k,i] + gamma * (eui * temp_P[k] - Lambda * self.Q[k,i])\n",
    "            \n",
    "            #计算更新后的bu 和 bi\n",
    "            self.bu[u] = self.bu[u] + gamma * (eui - Lambda * self.bu[u])\n",
    "            self.bi[i] = self.bi[i] + gamma * (eui - Lambda * self.bi[i])  \n",
    "            print(\"step: %d,  the RMSE is: %f\" % (step, rmse/len(uids)))\n",
    "            result[step]=rmse/len(uids)\n",
    "        print(\"SVD trained\") \n",
    "        \n",
    "        plt.plot(result, 'b-')\n",
    "        plt.show()\n",
    "    \n",
    "    def pred_SVD(self, uid, i_id):\n",
    "        #根据当前参数，预测用户uid对Item（i_id）的打分        \n",
    "        ans=self.mu + self.bi[i_id] + self.bu[uid] + np.dot(self.P[uid,:],self.Q[:,i_id])  \n",
    "        \n",
    "#         print(ans)\n",
    "\n",
    "        #将打分范围控制在0-1之间\n",
    "        if ans>1:  \n",
    "            return 1  \n",
    "        elif ans<0:  \n",
    "            return 0\n",
    "        return ans  \n",
    "\n",
    "    def sim_cal_UserCF(self, uid1, uid2 ):\n",
    "        #请补充基于用户的协同过滤中的两个用户uid1和uid2之间的相似度（根据两个用户对item打分的相似度）\n",
    "        #查找索引\n",
    "#         u1 = self.userIndex[uid1]\n",
    "#         u2 = self.userIndex[uid2]\n",
    "        #得到两个用户中相似的活动\n",
    "        itemShare = self.itemsForUser[uid1] & self.itemsForUser[uid2]\n",
    "#         print(self.itemsForUser[uid1] & self.itemsForUser[uid2])\n",
    "        #如果没有相似项填 NA\n",
    "        if len(itemShare) == 0:\n",
    "            similarity = 0\n",
    "        else:\n",
    "            #初始化变量\n",
    "            Ra = 0   #ra的平均值\n",
    "            Rb = 0   #rb的平均值\n",
    "            numerator = 0    #相似度的分子\n",
    "            denominator1 = 0  #分母\n",
    "            denominator2 = 0\n",
    "            \n",
    "            #计算用户A所有打分商品的平均分\n",
    "            for i in self.itemsForUser[uid1]:\n",
    "#                 u = self.userIndex[uid1]\n",
    "#                 i = self.eventIndex[eventItem]\n",
    "                Ra += self.userEventScores[uid1,i]\n",
    "            Ra = Ra/len(self.itemsForUser[uid1])\n",
    "            \n",
    "            #计算用户B所有打分商品的平均分\n",
    "            for i in self.itemsForUser[uid2]:\n",
    "#                 u = self.userIndex[uid2]\n",
    "#                 i = self.eventIndex[eventItem]\n",
    "                Rb += self.userEventScores[uid2,i]\n",
    "            Rb = Rb/len(self.itemsForUser[uid2])\n",
    "            \n",
    "            #计算相似度\n",
    "            for i in itemShare:\n",
    "#                 u1 = self.userIndex[uid1]\n",
    "#                 u2 = self.userIndex[uid2]\n",
    "#                 i = self.eventIndex[eventItem]\n",
    "                #分别计算分子和分母\n",
    "                numerator += (self.userEventScores[uid1,i] - Ra)*(self.userEventScores[uid2,i] - Rb)\n",
    "                denominator1 += (self.userEventScores[uid1,i] - Ra)**2\n",
    "                denominator2 += (self.userEventScores[uid2,i] - Rb)**2\n",
    "                \n",
    "#             print(\"Ra:%f, Rb:%f\"%(Ra, Rb))    \n",
    "            if denominator1*denominator2 == 0:\n",
    "                similarity = 0\n",
    "            else:    \n",
    "                similarity = numerator / (denominator1 ** 0.5)*(denominator2 ** 0.5)        \n",
    "\n",
    "        return similarity  \n",
    "\n",
    "    def userCFReco(self, userId, eventId):\n",
    "        \"\"\"\n",
    "        根据User-based协同过滤，得到event的推荐度\n",
    "        基本的伪代码思路如下：\n",
    "        for item i\n",
    "          for every other user v that has a preference for i\n",
    "            compute similarity s between u and v\n",
    "            incorporate v's preference for i weighted by s into running aversge\n",
    "        return top items ranked by weighted average\n",
    "        \"\"\"\n",
    "        #请补充完整代码\n",
    "        #索引\n",
    "        u = self.userIndex[userId]\n",
    "        i = self.eventIndex[eventId]\n",
    "        \n",
    "        #平均值初始化\n",
    "        Ra = 0\n",
    "        Rb = 0\n",
    "        \n",
    "        #计算userId自身对event的平均分\n",
    "        for each_i in self.itemsForUser[u]:\n",
    "#             u = self.userIndex[userId]\n",
    "#             i = self.eventIndex[eventItem]\n",
    "            Ra += self.userEventScores[u,each_i]\n",
    "#         print(self.itemsForUser[u])\n",
    "        if len(self.itemsForUser[u]) != 0:\n",
    "            Ra = Ra/len(self.itemsForUser[u])\n",
    "#             print(\"ra %f\"%(Ra))\n",
    "        numerator = 0\n",
    "        denominator = 0\n",
    "        \n",
    "        #基于用户协同过滤，得到推荐度\n",
    "        for each_u in self.usersForItem[i]:\n",
    "#             u = self.userIndex[eachUser]\n",
    "            if each_u != u:\n",
    "                for each_i in self.itemsForUser[each_u]:\n",
    "#                     i = self.eventIndex[eventItem] \n",
    "                    Rb += self.userEventScores[each_u,each_i]\n",
    "                #用户b对event的平均打分\n",
    "                Rb = Rb/len(self.itemsForUser[each_u])\n",
    "                numerator += self.sim_cal_UserCF(u, each_u)*(self.userEventScores[each_u,i]-Rb)\n",
    "                denominator += self.sim_cal_UserCF(u, each_u)\n",
    "        if denominator == 0:\n",
    "            ans = 0.0\n",
    "        else:\n",
    "            ans = Ra + numerator/denominator   \n",
    "            \n",
    "#         print(ans)    \n",
    "\n",
    "        return ans\n",
    "\n",
    "\n",
    "    def sim_cal_ItemCF(self, i_id1, i_id2):\n",
    "        #计算Item i_id1和i_id2之间的相似性\n",
    "        #请补充完整代码\n",
    "        #寻找共同的用户\n",
    "        userShare = self.usersForItem[i_id1] & self.usersForItem[i_id2]\n",
    "        if len(userShare) == 0:\n",
    "            similarity = 0.0\n",
    "        else:\n",
    "            #初始化\n",
    "            Ru = 0  #用户u对event打的平均分\n",
    "#             Rb = 0  \n",
    "            numerator = 0   #分子分母初始化\n",
    "            denominator1 = 0\n",
    "            denominator2 = 0\n",
    "\n",
    "            #计算相似度\n",
    "            for each_u in userShare:\n",
    "#                 u = self.userIndex[eachUser]\n",
    "                for each_i in self.itemsForUser[each_u]:\n",
    "#                     i = self.itemIndex[eachItem]\n",
    "                    Ru += self.userEventScores[each_u, each_i]\n",
    "                #用户u对events的平均打分\n",
    "                Ru = Ru/len(self.itemsForUser[each_u])\n",
    "#                 i1 = self.itemIndex[i_id1] \n",
    "#                 i2 = self.itemIndex[i_id2]\n",
    "                #计算分子分母\n",
    "                numerator += (self.userEventScores[each_u,i_id1] - Ru)*(self.userEventScores[each_u,i_id2] - Ru)\n",
    "                denominator1 += (self.userEventScores[each_u,i_id1] - Ru)**2\n",
    "                denominator2 += (self.userEventScores[each_u,i_id2] - Ru)**2\n",
    "            if denominator1*denominator2 == 0:\n",
    "                similarity = 0\n",
    "            else:\n",
    "                similarity = numerator / (denominator1 ** 0.5)*(denominator2 ** 0.5)\n",
    "\n",
    "        return similarity     \n",
    "        \n",
    "#     return num/den  \n",
    "            \n",
    "    def eventCFReco(self, userId, eventId):    \n",
    "        \"\"\"\n",
    "        根据基于物品的协同过滤，得到Event的推荐度\n",
    "        基本的伪代码思路如下：\n",
    "        for item i \n",
    "            for every item j tht u has a preference for\n",
    "                compute similarity s between i and j\n",
    "                add u's preference for j weighted by s to a running average\n",
    "        return top items, ranked by weighted average\n",
    "        \"\"\"\n",
    "        #请补充完整代码\n",
    "        #索引\n",
    "        u = self.userIndex[userId]\n",
    "        i = self.eventIndex[eventId]\n",
    "        \n",
    "        #初始化\n",
    "        numerator = 0\n",
    "        denominator = 0\n",
    "        \n",
    "        #计算推荐度\n",
    "        for each_i in self.itemsForUser[u]:\n",
    "            if each_i != i:\n",
    "                numerator += self.sim_cal_ItemCF(i, each_i) * self.userEventScores[u,i]\n",
    "                denominator += self.sim_cal_ItemCF(i, each_i)\n",
    "            \n",
    "        if denominator == 0:\n",
    "            ans = 0.0\n",
    "        else:\n",
    "            ans = numerator/denominator        \n",
    "#         print(ans)\n",
    "        return ans\n",
    "    \n",
    "    def svdCFReco(self, userId, eventId):\n",
    "        #基于模型的协同过滤, SVD++/LFM\n",
    "        u = self.userIndex[userId]\n",
    "        i = self.eventIndex[eventId]\n",
    "\n",
    "        return self.pred_SVD(u,i)\n",
    "\n",
    "    def userReco(self, userId, eventId):\n",
    "        \"\"\"\n",
    "        类似基于User-based协同过滤，只是用户之间的相似度由用户本身的属性得到，计算event的推荐度\n",
    "        基本的伪代码思路如下：\n",
    "        for item i\n",
    "          for every other user v that has a preference for i\n",
    "            compute similarity s between u and v\n",
    "            incorporate v's preference for i weighted by s into running aversge\n",
    "        return top items ranked by weighted average\n",
    "        \"\"\"\n",
    "        i = self.userIndex[userId]\n",
    "        j = self.eventIndex[eventId]\n",
    "\n",
    "        vs = self.userEventScores[:, j]\n",
    "        sims = self.userSimMatrix[i, :]\n",
    "\n",
    "        prod = sims * vs\n",
    "\n",
    "        try:\n",
    "              return prod[0, 0] - self.userEventScores[i, j]\n",
    "        except IndexError:\n",
    "              return 0\n",
    "\n",
    "    def eventReco(self, userId, eventId):\n",
    "        \"\"\"\n",
    "        类似基于Item-based协同过滤，只是item之间的相似度由item本身的属性得到，计算Event的推荐度\n",
    "        基本的伪代码思路如下：\n",
    "        for item i \n",
    "          for every item j that u has a preference for\n",
    "            compute similarity s between i and j\n",
    "            add u's preference for j weighted by s to a running average\n",
    "        return top items, ranked by weighted average\n",
    "        \"\"\"\n",
    "        i = self.userIndex[userId]\n",
    "        j = self.eventIndex[eventId]\n",
    "        js = self.userEventScores[i, :]\n",
    "        psim = self.eventPropSim[:, j]\n",
    "        csim = self.eventContSim[:, j]\n",
    "        pprod = js * psim\n",
    "        cprod = js * csim\n",
    "\n",
    "        pscore = 0\n",
    "        cscore = 0\n",
    "        try:\n",
    "              pscore = pprod[0, 0] - self.userEventScores[i, j]\n",
    "        except IndexError:\n",
    "              pass\n",
    "        try:\n",
    "              cscore = cprod[0, 0] - self.userEventScores[i, j]\n",
    "        except IndexError:\n",
    "              pass\n",
    "        return pscore, cscore\n",
    "\n",
    "    def userPop(self, userId):\n",
    "        \"\"\"\n",
    "        基于用户的朋友个数来推断用户的社交程度\n",
    "        主要的考量是如果用户的朋友非常多，可能会更倾向于参加各种社交活动\n",
    "        \"\"\"\n",
    "        if userId in self.userIndex:\n",
    "          i = self.userIndex[userId]\n",
    "          try:\n",
    "            return self.numFriends[0, i]\n",
    "          except IndexError:\n",
    "            return 0\n",
    "        else:\n",
    "              return 0\n",
    "\n",
    "    def friendInfluence(self, userId):\n",
    "        \"\"\"\n",
    "        朋友对用户的影响\n",
    "        主要考虑用户所有的朋友中，有多少是非常喜欢参加各种社交活动/event的\n",
    "        用户的朋友圈如果都积极参与各种event，可能会对当前用户有一定的影响\n",
    "        \"\"\"\n",
    "        nusers = np.shape(self.userFriends)[1]\n",
    "        i = self.userIndex[userId]\n",
    "        return (self.userFriends[i, :].sum(axis=0) / nusers)[0,0]\n",
    "\n",
    "    def eventPop(self, eventId):\n",
    "        \"\"\"\n",
    "        本活动本身的热度\n",
    "        主要是通过参与的人数来界定的\n",
    "        \"\"\"\n",
    "        i = self.eventIndex[eventId]\n",
    "        return self.eventPopularity[i, 0]\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generateRSData(RS, train=True, header=True):\n",
    "    \"\"\"\n",
    "    把前面user-based协同过滤 和 item-based协同过滤，以及各种热度和影响度作为特征组合在一起\n",
    "    生成新的训练数据，用于分类器分类使用\n",
    "    \"\"\"\n",
    "    fn = \"train.csv\" if train else \"test.csv\"\n",
    "    fin = open(fn, 'r')\n",
    "    fout = open(\"RS_\" + fn, 'w')\n",
    "    \n",
    "    #忽略第一行（列名字）\n",
    "    fin.readline().strip().split(\",\")\n",
    "    \n",
    "    # write output header\n",
    "    if header:\n",
    "      ocolnames = [\"userId\", \"eventId\", \"invited\", \"userCF_reco\", \"evtCF_reco\",\"svdCF_reco\"]\n",
    "      if train:\n",
    "        ocolnames.append(\"interested\")\n",
    "        ocolnames.append(\"not_interested\")\n",
    "      fout.write(\",\".join(ocolnames) + \"\\n\")\n",
    "    \n",
    "    ln = 0\n",
    "    for line in fin:\n",
    "      ln += 1\n",
    "      if ln%500 == 0:\n",
    "          print(\"%s:%d (userId, eventId)=(%s, %s)\" % (fn, ln, userId, eventId))\n",
    "          #break;\n",
    "      \n",
    "      cols = line.strip().split(\",\")\n",
    "      userId = cols[0]\n",
    "      eventId = cols[1]\n",
    "      invited = cols[2]\n",
    "      \n",
    "      userCF_reco = RS.userCFReco(userId, eventId)\n",
    "      itemCF_reco = RS.eventCFReco(userId, eventId)\n",
    "      svdCF_reco = RS.svdCFReco(userId, eventId)\n",
    "        \n",
    "#       user_reco = RS.userReco(userId, eventId)\n",
    "#       evt_p_reco, evt_c_reco = RS.eventReco(userId, eventId)\n",
    "#       user_pop = RS.userPop(userId)\n",
    "     \n",
    "#       frnd_infl = RS.friendInfluence(userId)\n",
    "#       evt_pop = RS.eventPop(eventId)\n",
    "      ocols = [userId,eventId,invited, userCF_reco, itemCF_reco, svdCF_reco]\n",
    "      \n",
    "      if train:\n",
    "        ocols.append(cols[4]) # interested\n",
    "        ocols.append(cols[5]) # not_interested\n",
    "      fout.write(\",\".join(map(lambda x: str(x), ocols)) + \"\\n\")\n",
    "    \n",
    "    fin.close()\n",
    "    fout.close()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "SVD Train...\n",
      "step: 0,  the RMSE is: 0.723530\n",
      "step: 1,  the RMSE is: 0.723530\n",
      "step: 2,  the RMSE is: 0.723530\n",
      "step: 3,  the RMSE is: 0.723530\n",
      "step: 4,  the RMSE is: 0.723524\n",
      "step: 5,  the RMSE is: 0.723523\n",
      "step: 6,  the RMSE is: 0.723523\n",
      "step: 7,  the RMSE is: 0.723520\n",
      "step: 8,  the RMSE is: 0.723519\n",
      "step: 9,  the RMSE is: 0.723516\n",
      "step: 10,  the RMSE is: 0.723514\n",
      "step: 11,  the RMSE is: 0.723510\n",
      "step: 12,  the RMSE is: 0.723507\n",
      "step: 13,  the RMSE is: 0.723502\n",
      "step: 14,  the RMSE is: 0.723496\n",
      "step: 15,  the RMSE is: 0.723489\n",
      "step: 16,  the RMSE is: 0.723483\n",
      "step: 17,  the RMSE is: 0.723477\n",
      "step: 18,  the RMSE is: 0.723471\n",
      "step: 19,  the RMSE is: 0.723465\n",
      "step: 20,  the RMSE is: 0.723459\n",
      "step: 21,  the RMSE is: 0.723454\n",
      "step: 22,  the RMSE is: 0.723448\n",
      "step: 23,  the RMSE is: 0.723443\n",
      "step: 24,  the RMSE is: 0.723438\n",
      "step: 25,  the RMSE is: 0.723433\n",
      "step: 26,  the RMSE is: 0.723428\n",
      "step: 27,  the RMSE is: 0.723423\n",
      "step: 28,  the RMSE is: 0.723419\n",
      "step: 29,  the RMSE is: 0.723414\n",
      "step: 30,  the RMSE is: 0.723410\n",
      "step: 31,  the RMSE is: 0.723406\n",
      "step: 32,  the RMSE is: 0.723402\n",
      "step: 33,  the RMSE is: 0.723399\n",
      "step: 34,  the RMSE is: 0.723395\n",
      "step: 35,  the RMSE is: 0.723392\n",
      "step: 36,  the RMSE is: 0.723389\n",
      "step: 37,  the RMSE is: 0.723386\n",
      "step: 38,  the RMSE is: 0.723383\n",
      "step: 39,  the RMSE is: 0.723380\n",
      "step: 40,  the RMSE is: 0.723377\n",
      "step: 41,  the RMSE is: 0.723375\n",
      "step: 42,  the RMSE is: 0.723372\n",
      "step: 43,  the RMSE is: 0.723370\n",
      "step: 44,  the RMSE is: 0.723368\n",
      "step: 45,  the RMSE is: 0.723366\n",
      "step: 46,  the RMSE is: 0.723364\n",
      "step: 47,  the RMSE is: 0.723362\n",
      "step: 48,  the RMSE is: 0.723360\n",
      "step: 49,  the RMSE is: 0.723359\n",
      "step: 50,  the RMSE is: 0.723357\n",
      "step: 51,  the RMSE is: 0.723355\n",
      "step: 52,  the RMSE is: 0.723354\n",
      "step: 53,  the RMSE is: 0.723353\n",
      "step: 54,  the RMSE is: 0.723351\n",
      "step: 55,  the RMSE is: 0.723350\n",
      "step: 56,  the RMSE is: 0.723349\n",
      "step: 57,  the RMSE is: 0.723348\n",
      "step: 58,  the RMSE is: 0.723347\n",
      "step: 59,  the RMSE is: 0.723346\n",
      "step: 60,  the RMSE is: 0.723345\n",
      "step: 61,  the RMSE is: 0.723344\n",
      "step: 62,  the RMSE is: 0.723343\n",
      "step: 63,  the RMSE is: 0.723342\n",
      "step: 64,  the RMSE is: 0.723341\n",
      "step: 65,  the RMSE is: 0.723340\n",
      "step: 66,  the RMSE is: 0.723340\n",
      "step: 67,  the RMSE is: 0.723339\n",
      "step: 68,  the RMSE is: 0.723338\n",
      "step: 69,  the RMSE is: 0.723338\n",
      "step: 70,  the RMSE is: 0.723337\n",
      "step: 71,  the RMSE is: 0.723337\n",
      "step: 72,  the RMSE is: 0.723336\n",
      "step: 73,  the RMSE is: 0.723335\n",
      "step: 74,  the RMSE is: 0.723335\n",
      "step: 75,  the RMSE is: 0.723335\n",
      "step: 76,  the RMSE is: 0.723334\n",
      "step: 77,  the RMSE is: 0.723334\n",
      "step: 78,  the RMSE is: 0.723333\n",
      "step: 79,  the RMSE is: 0.723333\n",
      "step: 80,  the RMSE is: 0.723333\n",
      "step: 81,  the RMSE is: 0.723332\n",
      "step: 82,  the RMSE is: 0.723332\n",
      "step: 83,  the RMSE is: 0.723332\n",
      "step: 84,  the RMSE is: 0.723331\n",
      "step: 85,  the RMSE is: 0.723331\n",
      "step: 86,  the RMSE is: 0.723331\n",
      "step: 87,  the RMSE is: 0.723331\n",
      "step: 88,  the RMSE is: 0.723330\n",
      "step: 89,  the RMSE is: 0.723330\n",
      "step: 90,  the RMSE is: 0.723330\n",
      "step: 91,  the RMSE is: 0.723330\n",
      "step: 92,  the RMSE is: 0.723330\n",
      "step: 93,  the RMSE is: 0.723329\n",
      "step: 94,  the RMSE is: 0.723329\n",
      "step: 95,  the RMSE is: 0.723329\n",
      "step: 96,  the RMSE is: 0.723329\n",
      "step: 97,  the RMSE is: 0.723329\n",
      "step: 98,  the RMSE is: 0.723329\n",
      "step: 99,  the RMSE is: 0.723328\n",
      "SVD trained\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nLs8nNX8dn5UPBRZKWgDMAE6JiMJzy6cCNwDLSFc0G+0pZtaStG6dEsrUqbByZerKfMABqfnsS19K\n0wGccgo8+CC8/36po7VyUW9X5HLlrsjW0r39NjzwAPzyl3D//fDOO9CpU5qS+dBD06u7NltNxXZF\ndnIxM957L92Lueee1ONs5Upo1Qr23Re+/nUYNiwNS9PKY3q0eE4u9XByMavd2rWpE8B996Urmnnz\nUvn226fmtcLSu/fGj2PlycmlHk4uZsVZuRL+7//SPZmHHoJXX03lO+8MX/5yWr74xXTvxsqfk0s9\nnFzMNl0EPPlkSjK/+x384Q/r5pz57GdTR4EDDoD99oP+/cGj/JUfJ5d6OLmYbb41a+CJJ1KS+eMf\n01KYqnm77eDf/g322Sfdu9l77zT7pjVvTi71cHIxa3hr18KSJekhzj//GR59ND3MWdC/f0oye++d\nphQYNAg6dixdvLbpnFzq4eRitmWsXg2PPQZz565bXn45bZNgl11Skhk0CD73uTTFQI8eblJrqpxc\n6uHkYlY6L7+cpnOeNy+9LlgAzz+/bnvXrmm8tN13T4Nw7rYb7LorVFQ46ZSak0s9nFzMmpbVq1OS\nWbRo3bJ4MbyZm4Rj221Tkunff93Sr1/qTNCuXelib0mcXOrh5GLW9EXASy+lJPP00+uWZ59NXaQL\nWrWCHXZISeazn03dpHfaCfr2Ta9dupTuHMpNscmlzZYIxszsk5DStAG9esHXvrb+ttWrU+eBpUvX\nLcuWpeFsCj3WCrp0STN29umTklBh6d07LZ/5TBqDzRqOk4uZNUtdusCQIWmpafVq+Nvf0rJ8ebqf\n8/zzKQE99FAaVy2vTZvUiaBnz5TIevaE7t1TWffu65YuXXzPp1hOLmZWdrp0SWOhDR684baI9ODn\n3/8OL74I1dXrXl96Kc3q+eCD8NZbG+7brh18+tPrL9tvn5aKivWXbt1g660b/1ybKicXM2tRpJR8\nunRJXZ/r8tZbqVfbihVpuuiXX06vr76aXqurU0+3lSvho49qP0aHDulh0vyy7bapN1zhNb8U4urU\nqfkPEurkYmZWi06d0rLLLhuvt3ZtaoZbtSotK1emez7/+Eda/+c/1y0vvACvvZaWtWvrPqaURjPY\nZhvo3HnD186dU2yF144d18XbseO6pUOHtJQiUTm5mJlthlat0lXIttumrtHFWLs2XRm9/npKNKtX\n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\nAABJRU5ErkJggg==\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x920a6d8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "生成训练数据...\n",
      "\n",
      "train.csv:500 (userId, eventId)=(123290209, 1887085024)\n",
      "train.csv:1000 (userId, eventId)=(272886293, 199858305)\n",
      "train.csv:1500 (userId, eventId)=(395305791, 1582270949)\n",
      "train.csv:2000 (userId, eventId)=(527523423, 3272728211)\n",
      "train.csv:2500 (userId, eventId)=(651258472, 792632006)\n",
      "train.csv:3000 (userId, eventId)=(811791433, 524756826)\n",
      "train.csv:3500 (userId, eventId)=(985547042, 1269035551)\n",
      "train.csv:4000 (userId, eventId)=(1107615001, 173949238)\n",
      "train.csv:4500 (userId, eventId)=(1236336671, 3849306291)\n",
      "train.csv:5000 (userId, eventId)=(1414301782, 2652356640)\n",
      "train.csv:5500 (userId, eventId)=(1595465532, 955398943)\n",
      "train.csv:6000 (userId, eventId)=(1747091728, 2131379889)\n",
      "train.csv:6500 (userId, eventId)=(1914182220, 955398943)\n",
      "train.csv:7000 (userId, eventId)=(2071842684, 1076364848)\n",
      "train.csv:7500 (userId, eventId)=(2217853337, 3051438735)\n",
      "train.csv:8000 (userId, eventId)=(2338481531, 2525447278)\n",
      "train.csv:8500 (userId, eventId)=(2489551967, 520657921)\n",
      "train.csv:9000 (userId, eventId)=(2650493630, 87962584)\n",
      "train.csv:9500 (userId, eventId)=(2791418962, 4223848259)\n",
      "train.csv:10000 (userId, eventId)=(2903662804, 2791462807)\n",
      "train.csv:10500 (userId, eventId)=(3036141956, 3929507420)\n",
      "train.csv:11000 (userId, eventId)=(3176074542, 3459485614)\n",
      "train.csv:11500 (userId, eventId)=(3285425249, 2271782630)\n",
      "train.csv:12000 (userId, eventId)=(3410667855, 1063772489)\n",
      "train.csv:12500 (userId, eventId)=(3531604778, 2584839423)\n",
      "train.csv:13000 (userId, eventId)=(3686871863, 53495098)\n",
      "train.csv:13500 (userId, eventId)=(3833637800, 2415873572)\n",
      "train.csv:14000 (userId, eventId)=(3944021305, 2096772901)\n",
      "train.csv:14500 (userId, eventId)=(4075466480, 3567240505)\n",
      "train.csv:15000 (userId, eventId)=(4197193550, 1628057176)\n"
     ]
    }
   ],
   "source": [
    "RS = RecommonderSystem()\n",
    "# print(RS.itemsForUser)\n",
    "print(\"生成训练数据...\\n\")\n",
    "generateRSData(RS,train=True,  header=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "时间、地点等特征都没有处理了，可以考虑用户看到event的时间与event开始时间的差、用户地点和event地点的差异。。。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
